package com.yeziji.config.aspect;

import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.google.common.collect.Lists;
import com.yeziji.annotation.RedisOperate;
import com.yeziji.annotation.RemoveRedis;
import com.yeziji.annotation.SaveRedis;
import com.yeziji.common.CommonSymbol;
import com.yeziji.constant.VariousStrPool;
import com.yeziji.service.cache.RedisService;
import com.yeziji.utils.ThreadPoolUtils;
import com.yeziji.utils.expansion.Lists2;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * redis 切面
 *
 * @author hwy
 * @since 2023/09/25 15:56
 **/
@Slf4j
@Aspect
@Component
public class RedisAspect {
    /**
     * #{xx} 正则
     */
    private static final String PARAM_REGEX = "\\{([^}]+)\\}";

    @Resource
    private RedisService redisService;

    @Around("@within(saveRedis) || @annotation(saveRedis)")
    public Object saveRedisAroundHandler(ProceedingJoinPoint pjp, SaveRedis saveRedis) throws Throwable {
        RedisOperate[] value = saveRedis.value();
        if (value != null) {
            // 获取入参
            final Object[] args = pjp.getArgs();
            List<RedisOperate> redisOperates = Lists.newArrayList(value);
            // 1. 先判断是否需要返回值, 如果需要的话就返回对应的缓存值(不要刷新缓存)
            List<RedisOperate> returns = redisOperates.stream().filter(RedisOperate::isReturn).collect(Collectors.toList());
            if (Lists2.isNotEmpty(returns)) {
                if (returns.size() > 1) {
                    throw new IllegalArgumentException("只能设置一个返回对象");
                } else {
                    // 存在返回对象就遍历查找, 如果不为空则返回
                    RedisOperate first = Lists2.getFirst(returns);
                    Object redisData;
                    final String key = this.buildKey(first, RedisOperate::key, args);
                    final String hashKey = this.buildKey(first, RedisOperate::hashKey, args);
                    if (StrUtil.isNotBlank(hashKey)) {
                        redisData = redisService.getByHash(key, hashKey);
                    } else {
                        redisData = redisService.get(key);
                    }
                    if (redisData != null) {
                        log.info("访问缓存: {}", StrUtil.isNotBlank(hashKey) ? key + CommonSymbol.HORIZONTAL_BAR + hashKey : key);
                        return redisData;
                    }
                }
            }
            // 2. 执行前的操作
            this.doBeforeRunnable(redisOperates, pjp.getArgs());
            // 3. 执行 aop 方法
            Object proceedObj = pjp.proceed();
            // 4. 执行 redis 操作
            for (RedisOperate redisOperate : redisOperates) {
                final String key = this.buildKey(redisOperate, RedisOperate::key, args);
                final String hashKey = this.buildKey(redisOperate, RedisOperate::hashKey, args);
                // 如果是覆盖, 不用理会是否存在
                if (redisOperate.override()) {
                    this.setRedis(redisOperate, key, hashKey, proceedObj);
                } else {
                    // 这里要判断是否存在 key 的 2 种方式
                    boolean onlyNotExistsKey =
                            StrUtil.isBlank(hashKey) && !redisService.isHasKey(key);
                    boolean notExistsHashKey =
                            StrUtil.isNotBlank(hashKey) && !redisService.isHasHashKey(key, hashKey);
                    if (onlyNotExistsKey || notExistsHashKey) {
                        this.setRedis(redisOperate, key, hashKey, proceedObj);
                    }
                }
                // 5. 执行后续操作
                this.doAfterRunnable(redisOperate, redisOperate.afterUsingParams() ? pjp.getArgs() : new Object[]{proceedObj});
            }
            return proceedObj;
        }
        return pjp.proceed();
    }

    @Around("@within(removeRedis) || @annotation(removeRedis)")
    public Object removeRedisAroundHandler(ProceedingJoinPoint pjp, RemoveRedis removeRedis) throws Throwable {
        RedisOperate[] value = removeRedis.value();
        if (value != null) {
            List<RedisOperate> redisOperates = Lists.newArrayList(value);
            // 1. 先判断是否有执行前的操作
            this.doBeforeRunnable(redisOperates, pjp.getArgs());
            // 2. 执行 aop 方法
            Object proceedObj = pjp.proceed();
            // 3. 执行 redis 操作
            for (RedisOperate redisOperate : redisOperates) {
                // 如果是覆盖, 不用理会是否存在
                if (StrUtil.isBlank(redisOperate.hashKey())) {
                    redisService.remove(redisOperate.key());
                } else {
                    redisService.removeForHash(redisOperate.key(), redisOperate.hashKey());
                }
                // 4. 判断是否有后续操作
                this.doAfterRunnable(redisOperate, redisOperate.afterUsingParams() ? pjp.getArgs() : new Object[]{proceedObj});
            }
            return proceedObj;
        }
        return pjp.proceed();
    }

    /**
     * 执行前置操作
     *
     * @param redisOperates redis 操作属性
     * @param args          操作数据
     */
    private void doBeforeRunnable(List<RedisOperate> redisOperates, Object... args) throws Exception {
        // 1. 先判断是否有执行前的操作
        List<RedisOperate> beforeOperates =
                redisOperates.stream()
                        .filter(operate -> operate.beforeRunnable() != null && StrUtil.isNotBlank(operate.beforeMethod()))
                        .collect(Collectors.toList());
        if (Lists2.isNotEmpty(beforeOperates)) {
            for (RedisOperate beforeOperate : beforeOperates) {
                // 判断是否需要入参, 如果需要只能取入参值; 不需要就直接执行
                if (beforeOperate.fillRunnableArgs()) {
                    this.instanceInvoke(
                            beforeOperate.beforeIsSpring(),
                            beforeOperate.beforeRunnable(),
                            beforeOperate.beforeMethod(),
                            args
                    );
                } else {
                    this.instanceInvoke(
                            beforeOperate.beforeIsSpring(),
                            beforeOperate.beforeRunnable(),
                            beforeOperate.beforeMethod()
                    );
                }
            }
        }
    }

    /**
     * 执行后继操作
     *
     * @param redisOperate redis 操作属性
     * @param args         传入参数
     */
    private void doAfterRunnable(RedisOperate redisOperate, Object... args) throws Exception {
        if (redisOperate.afterRunnable() != null && StrUtil.isNotBlank(redisOperate.afterMethod())) {
            // 判断是否需要入参, 如果需要只能取入参值; 不需要就直接执行
            if (redisOperate.fillRunnableArgs()) {
                this.instanceInvoke(
                        redisOperate.afterIsSpring(),
                        redisOperate.afterRunnable(),
                        redisOperate.afterMethod(),
                        args
                );
            } else {
                this.instanceInvoke(
                        redisOperate.afterIsSpring(),
                        redisOperate.afterRunnable(),
                        redisOperate.afterMethod()
                );
            }
        }
    }

    /**
     * 构建 key
     *
     * @param redisOperate   操作对象
     * @param getterFunction getter 函数
     * @param args           入参
     * @return {@link String} 构造指定携参 key
     */
    private String buildKey(RedisOperate redisOperate, Function<RedisOperate, String> getterFunction, Object[] args) {
        // 获取指定的字符串
        String getterStr = getterFunction.apply(redisOperate);
        if (StrUtil.isBlank(getterStr)) {
            return VariousStrPool.EMPTY;
        }
        if (args.length == 0) {
            return getterStr;
        }
        // 获取 #{} 进行拼接对应的入参 key
        Pattern pattern = Pattern.compile(PARAM_REGEX);
        Matcher matcher = pattern.matcher(getterStr);
        StringBuilder sb = new StringBuilder();
        int lastEnd = 0;
        while (matcher.find()) {
            sb.append(getterStr, lastEnd, matcher.start());
            // 遍历元素找到符合的数据对象
            String argFieldName = matcher.group(1);
            // 支持: #{0.code} 去查询 args 第一个参数的 code 属性
            if (argFieldName.contains(CommonSymbol.FULL_STOP)) {
                // 0.code
                List<String> argList = Lists2.splitter(CommonSymbol.FULL_STOP, argFieldName);
                if (!argList.isEmpty()) {
                    Object arg = null;
                    // 获取 args 的游标
                    String firstArg = argList.get(0);
                    if (NumberUtil.isNumber(firstArg)) {
                        arg = args[Integer.parseInt(firstArg)];
                    }
                    // 命中入参对象并且长度不为 1 的时候就进行循环找到最终入参: #{0.code} -> argList#size = 2
                    if (arg != null && argList.size() > 1) {
                        // 获取赋值长度, 避免 while 重复计算
                        final int argSize = argList.size() - 1;
                        int cursor = 1;
                        // 游标必须小于或等于数组长度
                        while (cursor <= argSize) {
                            // 获取 arg[fieldName] 的值
                            if ((cursor + 1) % 2 == 0) {
                                arg = ReflectUtil.getFieldValue(arg, argList.get(cursor));
                            } else {
                                // 如果是下标, 就继续获取对象的下标属性
                                Field[] declaredFields = arg.getClass().getDeclaredFields();
                                Field declaredField = declaredFields[cursor];
                                arg = ReflectUtil.getFieldValue(arg, declaredField);
                            }
                            cursor++;
                        }
                        sb.append(arg);
                    }
                    // 只有数字入参 #{0} 这种形式, 说明就是要获取 arg 的值
                    else if (arg != null) {
                        sb.append(arg);
                    }
                }
            } else {
                // 如果是数值就直接赋值
                if (NumberUtil.isNumber(argFieldName)) {
                    sb.append(args[Integer.parseInt(argFieldName)]);
                }
                // 否则是属性名称
                else {
                    for (Object arg : args) {
                        Object fieldValue = ReflectUtil.getFieldValue(arg, argFieldName);
                        if (fieldValue != null) {
                            sb.append(fieldValue);
                        }
                    }
                }
            }
            lastEnd = matcher.end();
        }
        sb.append(getterStr.substring(lastEnd));
        return sb.toString();
    }

    /**
     * 保存 redis
     *
     * @param redisOperate redis 操作
     * @param value        保存值
     */
    private void setRedis(RedisOperate redisOperate, String key, String hashKey, Object value) {
        if (StrUtil.isBlank(hashKey)) {
            if (redisOperate.async()) {
                redisService.asyncSet(key, value, redisOperate.time(), redisOperate.timeUnit(), ThreadPoolUtils.getCustomThreadPool());
            } else {
                redisService.set(key, value, redisOperate.time(), redisOperate.timeUnit());
            }
        } else {
            if (redisOperate.async()) {
                redisService.asyncSetForHash(key, hashKey, value, redisOperate.time(), redisOperate.timeUnit(), ThreadPoolUtils.getCustomThreadPool());
            } else {
                redisService.setForHash(key, hashKey, value, redisOperate.time(), redisOperate.timeUnit());
            }
        }
    }

    /**
     * 实例化执行某个方法
     *
     * @param isSpring      是否是 spring 容器
     * @param instanceClass 实例化 class
     * @param methodName    方法名
     * @param args          赋值参数
     */
    private void instanceInvoke(boolean isSpring, Class<?> instanceClass, String methodName, Object... args) throws Exception {
        Object instance;
        if (!isSpring) {
            instance = instanceClass.getDeclaredConstructor().newInstance();
        } else {
            instance = SpringUtil.getBean(instanceClass);
        }

        // 执行方法
        Method method = instanceClass.getMethod(methodName);
        if (args != null) {
            method.invoke(instance, args);
        } else {
            method.invoke(instance);
        }
    }

    public static void main(String[] args) {
        String input = "EFG:#{BCD}:#{EF}";
        String regex = "#\\{([^}]+)\\}";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(input);
        StringBuilder sb = new StringBuilder();
        int lastEnd = 0;
        while (matcher.find()) {
            sb.append(input, lastEnd, matcher.start());
            sb.append(matcher.group(1));
            lastEnd = matcher.end();
        }
        sb.append(input.substring(lastEnd));
        String output = sb.toString();
        System.out.println(output); // 输出: "A:BCD:EF"

        String txt = "0.code.1.username";
        String txt2 = "0";

        System.out.println("txt = " + Lists2.splitter(CommonSymbol.FULL_STOP, txt));
        System.out.println("txt2 = " + Lists2.splitter(CommonSymbol.FULL_STOP, txt2));
    }
}
